package org.uim.cricket.analyzer.analyze.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.uim.cricket.analyzer.analyze.Alogger;
import org.uim.cricket.analyzer.analyze.InningMappable;
import org.uim.cricket.analyzer.analyze.PlayerInitializable;
import org.uim.cricket.entities.MatchPlayer;
import org.uim.cricket.entities.Player;
import org.uim.cricket.entities.aggr.SingleInning;
import org.uim.cricket.entities.batting.BattingInning;
import org.uim.cricket.entities.batting.OutTypes;
import org.uim.cricket.entities.bowling.BowlingInning;
import org.uim.cricket.entities.fielding.FieldingTypes;
import org.uim.cricket.entities.fielding.SingleFielding;
import org.uim.cricket.entities.match.CricketMatch;
import org.uim.cricket.util.CricketFunctions;

public class PlayerInitializer implements PlayerInitializable {

	private CricketFunctions cf = new CricketFunctions();
	
	@Override
	public Map<String, List<MatchPlayer>> initMatchPlayers(InningMappable inningsMapper, CricketMatch match) {
		Map<String, List<MatchPlayer>> result = new HashMap<String, List<MatchPlayer>>();
		
		Alogger.printHeader("Executing [InningMappable: " + inningsMapper.getClass().getName() + "]...");
		Map<String, List<SingleInning>> innsMap = inningsMapper.getInningMap(match);
		Alogger.printHeader("Completed executing InningMappable.");
		
		if (innsMap == null) return null;
		
		Alogger.printSubHeader("   Iterating through teams...");
		for (String team : innsMap.keySet()) {
			
			
			List<SingleInning> tinns = innsMap.get(team);
			if (tinns == null) {
				Alogger.debug("     Team: " + team + " hasn't played any inning!"); 
				continue;
			}
			
			List<MatchPlayer> players = new ArrayList<MatchPlayer>();
			
			Alogger.debug("      iterating " + team + "'s innings...");
			for (int i=0; i<tinns.size(); i++) {
				
				SingleInning inn = tinns.get(i);
				if (inn == null) continue;
				
				List<BattingInning> batInns = inn.getBatInnings();
				if (batInns == null) continue;
				
				Alogger.debug("  iterating " + team + "'s batting innings...");
				for (int j=0; j<batInns.size(); j++) {
					
					BattingInning tmp = batInns.get(j);
					if (tmp == null || tmp.getBatsman() == null) continue;
					
					tmp.setBattedInning(inn.getInningMatch());
					
					int pindex = this.containInList(players, tmp.getBatsman().getTitleName());
					if (pindex < 0) {
						
						MatchPlayer newPlayer = new MatchPlayer();
						newPlayer.addBattingInning(tmp);
						newPlayer.setPlayer(tmp.getBatsman());
						players.add(newPlayer);
						
					} else {
						
						MatchPlayer existPlayer = players.get(pindex);
						existPlayer.addBattingInning(tmp);
						
					}
					
				}
				Alogger.debug("  Finished " + team + "'s batting innings.");
				
			}
			Alogger.debug(" Finished " + team + "'s innings.");
			
			result.put(team, players);
			
		}
		Alogger.printSubHeader("Finished iterating through teams.");
		
		Alogger.debug("------- Finding bowling innings for team " + match.getTeam1() + "..."); 
		this.allocateBowlingInnings(result, innsMap, match.getTeam1(), match.getTeam2());
		Alogger.debug("------- Finished " + match.getTeam1() + "."); 
		
		Alogger.debug("------- Finding bowling innings for team " + match.getTeam2() + "..."); 
		this.allocateBowlingInnings(result, innsMap, match.getTeam2(), match.getTeam1());
		Alogger.debug("------- Finished " + match.getTeam2() + "."); 
		
		Alogger.debug("------- Finding fielding efforts for team " + match.getTeam1() + "..."); 
		this.allocateFieldingInnings(result, innsMap, match.getTeam1(), match.getTeam2());
		Alogger.debug("------- Finished " + match.getTeam2() + "."); 
		
		Alogger.debug("------- Finding fielding efforts for team " + match.getTeam2() + "..."); 
		this.allocateFieldingInnings(result, innsMap, match.getTeam2(), match.getTeam1());
		Alogger.debug("------- Finished " + match.getTeam2() + "."); 
		
		Alogger.debug("------- Allocating wickets for bowlers...");
		this.allocateWickets(result, match);
		Alogger.debug("------- Finished.");
		
		return result;
	}

	private void allocateBowlingInnings(Map<String, List<MatchPlayer>> result, Map<String, List<SingleInning>> innsMap, 
						String bowlTeam, String batTeam) {

		List<SingleInning> inns = innsMap.get(batTeam);
		List<MatchPlayer> players = result.get(bowlTeam);
		
		if (players == null) return;
		if (inns == null) return;
		
		for (int i=0; i<inns.size(); i++) {
			
			SingleInning inn = inns.get(i);
			if (inn == null) continue;
			
			List<BowlingInning> bowlInns = inn.getBowlInnings();
			if (bowlInns == null) continue;
			
			for (int j=0; j<bowlInns.size(); j++) {
				
				BowlingInning bwlInn = bowlInns.get(j);
				
				if (bwlInn == null || bwlInn.getBowler() == null) continue;
				
				bwlInn.setBowlingInning(inn.getInningMatch());
				bwlInn.setBowlingPosition((j+1));
				int pindex = this.containInList(players, bwlInn.getBowler().getTitleName());
				if (pindex < 0) {
					
					MatchPlayer mplayer = new MatchPlayer();
					mplayer.setPlayer(bwlInn.getBowler());
					mplayer.addBowlingInning(bwlInn);
					players.add(mplayer);
					
				} else {
					
					MatchPlayer mplayer = players.get(pindex);
					mplayer.addBowlingInning(bwlInn);
					
				}
				
			}
			
			
		}
		
	}
	
	private void allocateWickets(Map<String, List<MatchPlayer>> result, CricketMatch match) {
		
		SingleInning[] innings = match.getInnings();
		if (innings == null) return;
		
		Comparator<BattingInning> batComp = new Comparator<BattingInning> () {

			@Override
			public int compare(BattingInning bat0, BattingInning bat1) {
				if (bat0 == null && bat1 != null) return -1;
				if (bat0 != null && bat1 == null) return 1;
				if (bat0 == null && bat1 == null) return 0;
				
				if (bat0.getOutDetails().getOutAsWicket() > bat1.getOutDetails().getOutAsWicket()) return 1;
				if (bat0.getOutDetails().getOutAsWicket() < bat1.getOutDetails().getOutAsWicket()) return -1;
				
				return 0;
			}
			
		};
		
		for (int i=0; i<innings.length; i++) {
			
			SingleInning inn = innings[i];
			if (inn == null) continue;
			
			HashMap<String, List<BattingInning>> ubowlers = new HashMap<String, List<BattingInning>>();
			
			List<BowlingInning> bowlInns = inn.getBowlInnings();
			List<BattingInning> batInns = inn.getBatInnings();
			
			if (batInns == null) continue;
			if (bowlInns == null) continue;
			
			for (int j=0; j<batInns.size(); j++) {
				BattingInning binn = batInns.get(j);
				if (binn == null || binn.getOutDetails() == null) continue;
				if (binn.getOutDetails().getBowler() == null || binn.getOutDetails().getBowler().getTitleName() == null) continue;
				if (binn.getOutDetails().getBowler().getTitleName().length() == 0) continue;
				
				String bwlName = binn.getOutDetails().getBowler().getTitleName().trim();
				List<BattingInning> lst = null;
				if (ubowlers.containsKey(bwlName)) {
					lst = ubowlers.get(bwlName);
					lst.add(binn);
				} else {
					lst = new ArrayList<BattingInning>();
					lst.add(binn);
					ubowlers.put(bwlName, lst);
				}
				
			}
			Alogger.debug(" No of bowlers: " + ubowlers.size());
			
			for (int j=0; j<bowlInns.size(); j++) {
				
				BowlingInning bowlinn = bowlInns.get(j);
				if (bowlinn == null || bowlinn.getStatistics() == null) continue;
				if (bowlinn.getStatistics().getWickets() <= 0) continue;
				
				Player bowler = bowlinn.getBowler();
				if (bowler == null || bowler.getTitleName() == null) continue;
				
				List<String> toremove = new ArrayList<String>();
				for (String bname : ubowlers.keySet()) {
					if (bowler.getTitleName().trim().equals(bname)) {
						List<BattingInning> lst = ubowlers.get(bname);
						for (BattingInning bi : lst) {
							bi.getOutDetails().setBowler(bowler);
						}
						bowlinn.setOutBatsmen(lst);
						toremove.add(bname);
					}
				}
				
				for (String rname : toremove) {
					ubowlers.remove(rname);
				}
			}
			
			for (int j=0; j<bowlInns.size(); j++) {
				
				BowlingInning bowlinn = bowlInns.get(j);
				if (bowlinn == null || bowlinn.getStatistics() == null) continue;
				if (bowlinn.getStatistics().getWickets() <= 0) continue;
				
				Player bowler = bowlinn.getBowler();
				if (bowler == null || bowler.getTitleName() == null) continue;
				if (bowlinn.getOutBatsmen() != null && bowlinn.getOutBatsmen().size() == bowlinn.getStatistics().getWickets()) {
					continue;
				}
				
				List<String> toremove = new ArrayList<String>();
				for (String bname : ubowlers.keySet()) {
					if (cf.arePlayersSame(bowler.getTitleName(), bname)) {
						List<BattingInning> lst = ubowlers.get(bname);
						for (BattingInning bi : lst) {
							bi.getOutDetails().setBowler(bowler);
						}
						bowlinn.setOutBatsmen(ubowlers.get(bname));
						toremove.add(bname);
					}
				}
				
				for (String rname : toremove) {
					ubowlers.remove(rname);
				}
			}
			
			if (ubowlers.size() != 0) {
				Alogger.error("   Not found corresponding bowlers for some batting inning outs!");
				for (String s : ubowlers.keySet()) {
					Alogger.error("    - " + s);
				}
			}
			
			for (int j=0; j<bowlInns.size(); j++) {
				
				BowlingInning bowl = bowlInns.get(j);
				if (bowl == null) continue;
				if (bowl.getStatistics().getWickets() <= 0) continue;
				
				if (bowl.getOutBatsmen() != null) {
					
					if (bowl.getStatistics().getWickets() != bowl.getOutBatsmen().size()) {
						Alogger.error("  Bowler Wickets: Not all the " + bowl.getBowler().getTitleName() + "'s wickets have been found!");
						Alogger.error("     batsman found: " + this.getVictimList(bowl.getOutBatsmen()));
					}
					
					Collections.sort(bowl.getOutBatsmen(), batComp);
					
				} else {
					
					Alogger.error("   Bowler wickets: " + bowl.getBowler().getTitleName() + " has taken " +
							bowl.getStatistics().getWickets() + ", but not found any victims!");
					
				}
				
			}
			
		}
		
	}
	
	private String getVictimList(List<BattingInning> bis) {
		StringBuilder sb = new StringBuilder();
		int count = 0;
		
		for (int i=0;i<bis.size();i++) {
			BattingInning b = bis.get(i);
			if (b==null) continue;
			if (b.getBatsman() != null && b.getBatsman().getTitleName() != null) {
				if (count > 0) sb.append(", ");
				sb.append(b.getBatsman().getTitleName());
				count++;
			}
		}
		
		return sb.toString();
	}
	
	private void allocateFieldingInnings(Map<String, List<MatchPlayer>> result, Map<String, List<SingleInning>> innsMap, 
						String fieldTeam, String batTeam) {
		
		List<SingleInning> inns = innsMap.get(batTeam);
		List<MatchPlayer> players = result.get(fieldTeam);
		
		if (players == null) return;
		if (inns == null) return;
		
		for (int i=0; i<inns.size(); i++) {
			
			SingleInning inn = inns.get(i);
			if (inn == null) continue;
			
			List<BattingInning> batInns = inn.getBatInnings();
			if (batInns == null) continue;
			
			for (int j=0; j<batInns.size(); j++) {
				
				BattingInning batInn = batInns.get(j);
				if (batInn == null || batInn.getBatsman() == null) continue;
				if (batInn.getOutDetails() == null) continue;
				
				Player[] fielders = batInn.getOutDetails().getFielders();
				if (fielders == null) continue;
				OutTypes batsmanOutType = batInn.getOutDetails().getOutType();
				
				for (int k=0; k<fielders.length; k++) {
					
					Player fielder = fielders[k];
					if (fielder == null) continue;
				
					SingleFielding fielding = new SingleFielding();
					if (batsmanOutType == OutTypes.COUGHT) fielding.setFieldType(FieldingTypes.CATCH);
					else if (batsmanOutType == OutTypes.COUGHT_BY_BOWLER) fielding.setFieldType(FieldingTypes.CATCH_OWN_BOWLING);
					else if (batsmanOutType == OutTypes.COUGHT_BY_KEEPER) fielding.setFieldType(FieldingTypes.CATCH_WICKETKEEPER);
					else if (batsmanOutType == OutTypes.RUNOUT) fielding.setFieldType(FieldingTypes.RUNOUT);
					else if (batsmanOutType == OutTypes.STUMPED) fielding.setFieldType(FieldingTypes.STUMPED);
					else {
						Alogger.debug("  Unknown fielding type is found!");
						Alogger.debug("     batsman: [" + batInn.getBatsman().getTitleName() + "]");
						fielding.setFieldType(FieldingTypes.OTHER);
					}
					fielding.setInning(inn.getInningMatch());
					fielding.setBatsman(batInn.getBatsman());

					int pindex = this.containInList(players, fielder.getTitleName());
					if (pindex >= 0) {
						
						MatchPlayer mplayer = players.get(pindex);
						fielders[k] = mplayer.getPlayer();
						mplayer.addFieldingEffort(fielding);
						
					}
					
				}
				
				batInn.getOutDetails().setFielders(fielders);
				
			}
			
			
		}
		
	}
	
	private int containInList(List<MatchPlayer> list, String player) {
    	
    	if (list == null) return -1;
        
        for (int i=0;i<list.size();i++) {
            MatchPlayer tmp = list.get(i);
            if (tmp == null || tmp.getPlayer() == null) continue;
            if (tmp.getPlayer().getTitleName() == null) continue;
            
            if (cf.arePlayersSame(tmp.getPlayer().getTitleName(), player)) {
                return i;
            }
        }
        return -1;
        
    }
	
	/*
	private int containInBowlList(List<BowlingInning> bowlInns, Player bowler) {
		
		if (bowler == null) return -1;
		
		for (int i=0; i<bowlInns.size(); i++) {
			BowlingInning binn = bowlInns.get(i);
			if (binn == null) continue;
			
			if (cf.arePlayersSame(binn.getBowler().getTitleName(), bowler.getTitleName())) {
				return i;
			}
		}
		
		return -1;
		
	}
	
	
	private List<BattingInning> containInBatsmanList(List<BattingInning> batInns, Player bowler) {
		
		if (bowler == null) return null;
		
		List<BattingInning> wkts = new ArrayList<BattingInning>();
		for (int i=0; i<batInns.size(); i++) {
			BattingInning binn = batInns.get(i);
			if (binn == null || binn.getOutDetails() == null) continue;
			if (binn.getOutDetails().getBowler() == null || binn.getOutDetails().getBowler().getTitleName() == null) continue;
			if (binn.getOutDetails().getBowler().getTitleName().length() == 0) continue;
			
			if (cf.arePlayersSame(bowler.getTitleName(), binn.getOutDetails().getBowler().getTitleName())) {
				wkts.add(binn);
				binn.getOutDetails().setBowler(bowler);
			}
		}
		
		return wkts;
		
	}
	*/
	
}
